#include "sqlite3.h"

#include "Util.h"
#include "User.h"

using namespace scgi;

std::string User::get(std::string key_) const
{
	const_iterator it1 = table.find(key_);
	if(it1 != table.end()) return it1->second;

	return "";
}

void User::set(std::string key_, std::string value_)
{
	table[key_] = value_;
}

User::const_iterator User::begin() const { return table.begin(); }

User::const_iterator User::end() const { return table.end(); }

std::string User::to_string()
{
	std::string buffer;
	buffer.reserve(1024);

	URLEncoder encode;

	const_iterator it1 = begin();
	while(it1 != end())
	{
		buffer.append(encode(it1->first.begin(), it1->first.end()));
		buffer.append(1, '=');
		buffer.append(encode(it1->second.begin(), it1->second.end()));
		if(++it1 != table.end()) buffer.append(1, '&');
	}

	return std::move(buffer);
}

std::string User::get_uid_string() const
{
	return scgi::to_string(uid);
}


UserTable::UserTable(size_t try_count_, size_t cache_size_)
	: db(NULL)
	, stmt_create(NULL)
	, stmt_select_by_uid(NULL)
	, stmt_select_by_name(NULL)
	, stmt_update(NULL)
	, stmt_remove_by_uid(NULL)
	, try_count(try_count_)
	, cache(cache_size_) { }

UserTable::~UserTable()
{
	mutex.lock();
	if(stmt_create) ::sqlite3_finalize(stmt_create);
	if(stmt_select_by_uid) ::sqlite3_finalize(stmt_select_by_uid);
	if(stmt_select_by_name) ::sqlite3_finalize(stmt_select_by_name);
	if(stmt_update) ::sqlite3_finalize(stmt_update);
	if(stmt_remove_by_uid) ::sqlite3_finalize(stmt_remove_by_uid);
	if(db) ::sqlite3_close(db);
	mutex.unlock();
}

size_t UserTable::get_cache_size() { return cache.size(); }

User::pointer UserTable::create(std::string name_)
{
	User::pointer user;

	mutex.lock();
	user = create_record(name_);
	mutex.unlock();

	return user;
}

int UserTable::remove(unsigned int uid_)
{
	cache.get(uid_);

	mutex.lock();
	int result = remove_record_by_uid(uid_);
	mutex.unlock();

	return result;
}

User::pointer UserTable::get(unsigned int uid_)
{
	User::pointer user;

	user = cache.get(uid_);
	if(user) return user;

	mutex.lock();
	user = select_record_by_uid(uid_);
	mutex.unlock();

	return user;
}

int UserTable::set(User::pointer user_)
{
	mutex.lock();
	int result = update_record(user_);
	mutex.unlock();

	if(result) throw 500;

	cache.set(user_->uid, user_);

	return 0;
}

int UserTable::open_database(std::string path_)
{
	int result = ::sqlite3_open(path_.c_str(), &db);
	return result;
}

int UserTable::create_table()
{
	int result = ::sqlite3_exec(
		  db
		, "CREATE TABLE IF NOT EXISTS user("
			"uid INTEGER PRIMARY KEY AUTOINCREMENT, "
			"name TEXT UNIQUE NOT NULL, "
			"data TEXT);"
		, NULL
		, NULL
		, NULL);
	if(result == SQLITE_OK)
	{
		result = ::sqlite3_exec(
			  db
			, "CREATE INDEX IF NOT EXISTS user_index ON user(name);"
			, NULL
			, NULL
			, NULL);
	}

	return result;
}

int UserTable::initialize(Server * server_ptr_)
{
	server_ptr = server_ptr_;

	std::string sql_create(
		"INSERT INTO user(name) VALUES(?);");
	int result = ::sqlite3_prepare_v2(
		db, sql_create.c_str(), sql_create.size(), &stmt_create, NULL);
	if(result == SQLITE_OK)
	{
		std::string sql_select_by_uid("SELECT * FROM user WHERE uid = ?;");
		result = ::sqlite3_prepare_v2(
			  db
			, sql_select_by_uid.c_str()
			, sql_select_by_uid.size()
			, &stmt_select_by_uid
			, NULL);
	}
	if(result == SQLITE_OK)
	{
		std::string sql_select_by_name("SELECT * FROM user WHERE name = ?;");
		result = ::sqlite3_prepare_v2(
			  db
			, sql_select_by_name.c_str()
			, sql_select_by_name.size()
			, &stmt_select_by_name
			, NULL);
	}
	if(result == SQLITE_OK)
	{
		std::string sql_update(
			"UPDATE user SET name = ?, data = ? WHERE uid = ?;");
		result = ::sqlite3_prepare_v2(
			db, sql_update.c_str(), sql_update.size(), &stmt_update, NULL);
	}
	if(result == SQLITE_OK)
	{
		std::string sql_remove_by_uid(
			"DELETE FROM user WHERE uid = ?;");
		result = ::sqlite3_prepare_v2(
			  db
			, sql_remove_by_uid.c_str()
			, sql_remove_by_uid.size()
			, &stmt_remove_by_uid
			, NULL);
	}

	return result;
}

User::pointer UserTable::create_record(std::string name_)
{
	int result;

	for(size_t i = 0; i < try_count; i++)
	{
		::sqlite3_reset(stmt_create);
		result = ::sqlite3_bind_text(
			  stmt_create
			, 1
			, name_.c_str()
			, name_.size()
			, SQLITE_TRANSIENT);
		if(result != SQLITE_OK) break;

		result = ::sqlite3_step(stmt_create);
		if(
			   result == SQLITE_OK
			|| result == SQLITE_DONE
			|| result == SQLITE_CONSTRAINT) break;

		::sqlite3_sleep(1);
	}
		
	User::pointer user;

	if(result == SQLITE_OK || result == SQLITE_DONE)
	{
		user = User::pointer(new User);
		user->name = name_;
	}

	return user;
}

int UserTable::remove_record_by_uid(unsigned int uid_)
{
	int result = ::sqlite3_reset(stmt_remove_by_uid);
	if(result == SQLITE_ROW || result == SQLITE_OK)
	{
		result = ::sqlite3_bind_int(stmt_remove_by_uid, 1, uid_);
	}
	if(result != SQLITE_OK) return result;

	for(size_t i = 0; i < try_count; i++)
	{
		result = ::sqlite3_step(stmt_remove_by_uid);
		if(result == SQLITE_BUSY)
		{
			assert(false); // Database file already open by another process.
			::sqlite3_sleep(1);
			continue;
		}
		break;
	}

	return (result == SQLITE_OK || result == SQLITE_DONE)
		? SQLITE_OK : result;
}

User::pointer UserTable::select_record_by_uid(unsigned int uid_)
{
	User::pointer user;

	int result = ::sqlite3_reset(stmt_select_by_uid);
	if(result != SQLITE_ROW && result != SQLITE_OK) return user;

	result = ::sqlite3_bind_int(stmt_select_by_uid, 1, uid_);
	if(result != SQLITE_OK) return user;

	for(size_t i = 0; i < try_count; i++)
	{
		result = ::sqlite3_step(stmt_select_by_uid);
		if(result == SQLITE_BUSY)
		{
			assert(false); // Database file already open by another process.
			::sqlite3_sleep(1);
			continue;
		}
		break;
	}
		
	if(result == SQLITE_DONE || result == SQLITE_ROW)
	{
		user = User::pointer(new User);
		
		user->uid = ::sqlite3_column_int(stmt_select_by_uid, 0);

		char const * first = reinterpret_cast<char const *>(
			::sqlite3_column_text(stmt_select_by_uid, 1));
		int length = ::sqlite3_column_bytes(stmt_select_by_uid, 1);
		std::string name(first, first + length);
		user->name = name;

		first = reinterpret_cast<char const *>(
			::sqlite3_column_text(stmt_select_by_uid, 2));
		length = ::sqlite3_column_bytes(stmt_select_by_uid, 2);
		user->assign(first, first + length);
	}

	return user;
}

User::pointer UserTable::select_record_by_name(std::string name_)
{
	User::pointer user;

	int result = ::sqlite3_reset(stmt_select_by_name);
	if(result != SQLITE_ROW && result != SQLITE_OK) return user;

	result = ::sqlite3_bind_text(
		  stmt_select_by_name
		, 1
		, name_.c_str()
		, name_.size()
		, SQLITE_TRANSIENT);
	if(result != SQLITE_OK) return user;

	for(size_t i = 0; i < try_count; i++)
	{
		result = ::sqlite3_step(stmt_select_by_name);
		if(result == SQLITE_BUSY)
		{
			assert(false); // Database file already open by another process.
			::sqlite3_sleep(1);
			continue;
		}
		break;
	}
		
	if(result == SQLITE_DONE || result == SQLITE_ROW)
	{
		user = User::pointer(new User);
		
		user->uid = ::sqlite3_column_int(stmt_select_by_name, 0);

		char const * first = reinterpret_cast<char const *>(
			::sqlite3_column_text(stmt_select_by_name, 1));
		int length = ::sqlite3_column_bytes(stmt_select_by_name, 1);
		std::string name(first, first + length);
		user->name = name;

		first = reinterpret_cast<char const *>(
			::sqlite3_column_text(stmt_select_by_name, 2));
		length = ::sqlite3_column_bytes(stmt_select_by_name, 2);
		user->assign(first, first + length);
	}

	return user;
}

int UserTable::update_record(User::pointer user_)
{
	int result = ::sqlite3_reset(stmt_update);
	if(result == SQLITE_ROW || result == SQLITE_OK)
	{
		result = ::sqlite3_bind_text(
			stmt_update, 1, user_->name.c_str(), user_->name.size(), SQLITE_TRANSIENT);
	}
	if(result == SQLITE_OK)
	{
		std::string data = user_->to_string();
		result = ::sqlite3_bind_text(
			stmt_update, 2, data.c_str(), data.size(), SQLITE_TRANSIENT);
	}
	if(result == SQLITE_OK)
	{
		result = ::sqlite3_bind_int(stmt_update, 3, user_->uid);
	}
	if(result != SQLITE_OK) return result;

	for(size_t i = 0; i < try_count; i++)
	{
		result = ::sqlite3_step(stmt_update);
		if(result == SQLITE_BUSY)
		{
			assert(false); // Database file already open by another process.
			::sqlite3_sleep(1);
			continue;
		}
		break;
	}

	return result == SQLITE_DONE ? SQLITE_OK : result;
}




